# kernel.rb
# Copyright (C) 2006 Akira TAGOH

# Authors:
#   Akira TAGOH  <at@gclab.org>

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

require 'rbconfig'
require 'thread'
require 'singleton'


class ObjectCache
  include Singleton

  class << self

    def each_objects(&block)
      oc = ObjectCache.instance
      oc.cache.each(&block)
    end # def each_objects

    def each_object(file, &block)
      oc = ObjectCache.instance
      oc.cache[file].each(&block)
    end # def each_object

    def delete(file)
      oc = ObjectCache.instance
      oc.cache.delete(file)
    end # def delete

    def clear
      oc = ObjectCache.instance
      oc.cache.clear
    end # def clear

    def cached?(file)
      oc = ObjectCache.instance
      return oc.cache.has_key?(file)
    end # def cached?

    def loaded(file, klasses)
      oc = ObjectCache.instance
      klasses = [klasses] unless klasses.kind_of?(Array)
      klasses.delete_if do |x|
        result = false
        each_objects do |k, v|
          result |= v.include?(x)
        end
        result
      end
      oc.cache[file] = klasses
    end # def loaded

  end

  def initialize
    @cache = {}
    @cache.default = []
  end # def initialize

  attr_reader :cache

end # class ObjectCache

class Object

  def require(file, *args)
    retval = false

    begin
      retval = load("#{file}.rb", *args)
    rescue LoadError => e
      if e =~ /#{file}\Z/ then
        begin
          # the module that now is attempting to load may be a dynamic loadable module.
          # So giving up to manage to load as the anonymous module.
          ::Kernel.require(file)
        rescue LoadError
          raise LoadError.new("no such file to load -- #{file}")
        end
      else
        raise
      end
    end

    return retval
  end # def require

  def load(file, *args)
    unless args.empty? then
      return ::Kernel.load(file, *args)
    end
    if ObjectCache.cached?(file) then
      return true
    end

    retval = nil

    Thread.exclusive do
      oldlist = objectlist
      retval = ::Kernel.load(file, true)
      newlist = objectlist
      ObjectCache.loaded(file, newlist.uniq - oldlist.uniq) if retval
    end

    return retval
  end # def load

  private

  def objectlist
    list = []
    ObjectSpace.each_object(Class) do |klass|
      list.push(klass)
    end
    ObjectSpace.each_object(Module) do |mod|
      list.push(mod)
    end

    return list
  end # def objectlist

end # class Object

class Module

  alias :original_const_missing :const_missing

  def const_missing(klass_id)
    brief_name = self == Object ? '' : "#{name.sub(/\A#<Module:0x[0-9a-z]+>::/, '')}::"
    ObjectCache.each_objects do |file, klasses|
      klasses.each do |klass|
        if klass.to_s =~ /\A#<Module:0x[0-9a-z]+>::#{brief_name}#{klass_id}\Z/ then
          return klass
        end
      end
    end
    ObjectCache.each_objects do |file, klasses|
      klasses.each do |klass|
        if klass.to_s =~ /\A#<Module:0x[0-9a-z]+>::#{klass_id}\Z/ then
          return klass
        end
      end
    end

    raise NameError.new("uninitialized constant #{brief_name}#{klass_id}")
  end # def const_missing

end # class Module

class Class

  def const_missing(klass_id)
    if [Object, Kernel].include?(self) then
      super
    else
      begin
        Object.send(:const_missing, klass_id)
      rescue NameError
        name = self.to_s.sub(/\A#<Module:0x[0-9a-z]+>::/, '')
        klass = nil
        begin
          klass = eval(name)
        rescue NameError
          raise NameError.new("uninitialized constant #{klass_id}")
        end

        return klass.const_get(klass_id)
      end
    end
  end # def const_missing

end # class Class
